#include "state_intro.h"
#include "krryn.h"
#include "string_util.h"

#include "render_control.h"
#include "widget_factory.h"

#include "md5_mesh_parser.h"
#include "md5_anim_parser.h"
#include "md5_nodes.h"
#include "texture.h"
#include "renderer.h"
#include "tabs.h"
#include "diagram.h"
#include "md5_mesh_builder.h"

#include "animation_diagram.h"
#include "opengl.h"

#include "skeleton.h"
#include "animation.h"
#include "expression_tree.h"
#include "expression_root.h"
#include "expression_animate.h"

#include <cg/cg.h>

using namespace krryn::math;
using namespace krryn::animation;
using namespace krryn::system;
using namespace krryn::usr;
using namespace krryn::core;
using namespace krryn::view;
using namespace krryn::gui;

// DONE:
// - add automatic shader variable binding, based on the name (eg "mvp" gets the model-view-projection matrix automatically)
//		- may need some kind of naming convention eg.
// - make fbo output available to shader trough texture
//
// TODO:
// - add pbo support
// - implement VSM (variance shadow mapping)
// - Implement deferred rendering
//		- Optimized (stencil buffer?) light pass
//		- Highlevel approach to giving objects to the renderer (eg. here's a mesh, and some lights, fix it)
//		- Look into doing partial depth-pre pass (as ATi recommends it)
// - add lights:
//		- point
//		- spotlight
//		- direct light
//		- make it extensible
//
// HACK THAT TOGETHER, then refactor:
// - Once we need alpha, we should migrate to Inferred lighting
//
// Long term TODO:
// - Add FSAA/MSAA/CSAA/SSAA whatever
// - Add particle system
// - Add animation system
// - Add physics system
// - Add AI system
//		(Already have time-sliced A-star implementation)
// - Add VERSE support
// - Add python bindings
// - Resource management
//		- Should allow for background loading (while movie is playing)
//		- Support compressed archives (zip?)
// - Add BVH for fast ray-tracing (eg. object picking).
//   Must be triangle-accurate
// - Fix OpenGL debugger to support direct opengl calls (eg. from non extensions)
//		- Move current injection code to opengl32.dll's wrapper
//			That way I can also see what 3rd party libs are doing

enum pixel_format{
	format_a8r8g8b8,
};

class pixel_buffer : public hardware_buffer{
public:
	pixel_buffer(int a_Width, int a_Height, pixel_format a_Format)
		: hardware_buffer(0), m_Format(a_Format),  m_Width(a_Width), m_Height(a_Height)
	{
		m_SizeInBytes = get_bpp(a_Format) * m_Width * m_Height;
	}

	pixel_format get_pixel_format() const { return m_Format; }
	int get_width() const { return m_Width; }
	int get_height() const { return m_Height; }
private:
	size_t get_bpp(pixel_format a_Format)
	{
		switch(a_Format){
			case format_a8r8g8b8:
				return sizeof(unsigned long);
		}

		throw std::runtime_error("Invalid pixel format specified");
	}

	int m_Width;
	int m_Height;
	pixel_format m_Format;
};

class pixel_buffer_opengl : public pixel_buffer{
public:
	pixel_buffer_opengl(int a_Width, int a_Height, pixel_format a_Format)
		: pixel_buffer(a_Width, a_Height, a_Format)
	{
	}

	virtual void write_data(size_t a_OffsetInBytes, size_t a_SizeInBytes, const void *a_Source){
	}

	virtual void read_data(size_t a_OffsetInBytes, size_t a_SizeInBytes, void *a_Destination){
	}

protected:
	virtual void* lock_impl(){
		if(locked() != write){
			// return downloaded image from GPU
		}else{
			// return empty image
		}
	}

	virtual void unlock_impl(){
		if(locked() != read){
			// upload image to GPU
		}
	}
};
#if 0
struct _temp_anim_info{
	float last_time;
	float max_time;

	int curr_frame;
	int next_frame;
} m_AnimInfo = {0, 0, 0, 1};

void _temp_animate(krryn::data::md5_animation *a_Anim, _temp_anim_info *a_Info, float a_DT){
	int maxFrames = a_Anim->m_NumFrames - 1;

	a_Info->last_time += a_DT;

	if(a_Info->last_time >= a_Info->max_time){
		a_Info->curr_frame++;
		a_Info->next_frame++;
		a_Info->last_time = 0.f;

		if(a_Info->curr_frame > maxFrames)
			a_Info->curr_frame = 0;

		if(a_Info->next_frame > maxFrames)
			a_Info->next_frame = 0;
	}
}

void _temp_interp_skel(krryn::data::md5_joint *a_S1, krryn::data::md5_joint *a_S2, int a_NumJoints, float t, krryn::data::md5_joint *a_Res){
	for(int i = 0; i < a_NumJoints; i++){
		a_Res[i].m_Parent = a_S1[i].m_Parent;
		a_Res[i].m_Position = a_S1[i].m_Position.simple_linear_combination(a_S2[i].m_Position, t);
		a_Res[i].m_Rotation = a_S1[i].m_Rotation.nlerp(a_S2[i].m_Rotation, t);
	}
}
#endif

render_operation diamond(renderer& r, float h, float w){
	render_operation op;

	op.m_DrawMode = mode_triangle_list;
	int l_Offset = op.m_Format.append_element(0, type_float3, semantic_position);

	op.m_VertexBuffer = r.create_vertex_buffer(op.m_Format.get_size_in_bytes(), 6);
	op.m_IndexBuffer  = r.create_index_buffer(24, index_16bit);

	float* vb = (float*)op.m_VertexBuffer->lock(hardware_buffer::write);

	*vb++ =  0; *vb++ =      h; *vb++ =  0; // top
	*vb++ = -w; *vb++ =      0; *vb++ = -w;
	*vb++ = -w; *vb++ =      0; *vb++ =  w;
	*vb++ =  w; *vb++ =      0; *vb++ = -w;
	*vb++ =  w; *vb++ =      0; *vb++ =  w;
	*vb++ =  0; *vb++ = -3 * h; *vb++ =  0; // bottom

	op.m_VertexBuffer->unlock();

	short* ib = (short*)op.m_IndexBuffer->lock(hardware_buffer::write);

	for(int i = 0; i <= 5; i += 5){
		*ib++ = i; *ib++ = 1; *ib++ = 2;
		*ib++ = i; *ib++ = 2; *ib++ = 4;
		*ib++ = i; *ib++ = 4; *ib++ = 3;
		*ib++ = i; *ib++ = 3; *ib++ = 1;
	}

	op.m_IndexBuffer->unlock();

	return op;
}

/////////////////////////////////////////////
namespace{
	renderer *l_Renderer;
	render_control *l_Control;
	diagram *m_Diagram;
	refc_ptr<render_target> l_Targets;
	window* m_Window;
	tabs* m_Tabs;
	render_operation m_Operation;
	render_operation m_Diamond;
	code_highlighter *m_Code;
	//label *m_TempLabel;
	shader *s, *m_GeometryPass;
	krryn::data::md5_mesh_builder b("data/models/monsters/teleport_dropper");
	krryn::view::texture *tex;
	keyboard *m_Keyboard;
	mouse *m_Mouse;
	//krryn::data::md5_joint *m_Skeleton;
	expression_tree *m_Exp;
}

static state_intro _state_intro;

state_intro::~state_intro(){}

void compile_error_handler(compile_error_args *a_Args){
	//m_TempLabel->set_text(a_Args->m_ErrorMessage);
}

void state_intro::init(){
	// generic stuff that you always have to do
	system_info i;
	
	//m_Manager = new job_manager(i.number_of_processors());
	m_Window = window::with()
		.top(100)
		.left(100)
		.width(800)
		.height(600)
	.create();

	m_Tabs = tabs::with()
		.parent(m_Window)
	.create();

	m_Diagram = diagram::with()
		.width(800)
		.height(600)
		.parent(m_Tabs->named_panel("Behavior"))
		.factory(new diagram_factory_default())
	.create();

#if 1
	m_Diagram->create_node("Animation: Idle");
	m_Diagram->create_node("Animation: Run", 667)->move_to(100, 100);
	m_Diagram->create_node("blend", 668)->move_to(-100, 100);
	m_Diagram->create_node("output", 669)->move_to(-100, 200);

	m_Code = code_highlighter::with()
		.width(800)
		.height(600)
		.parent(m_Tabs->named_panel("Shader"))
	.create();

	l_Control = render_control::with()
		.width(800)
		.parent(m_Tabs->named_panel("Renderer"))
		.height(600)
		.api("opengl")
		.vsync(false)
	.create();
#endif
	//m_TempLabel = label::with()
	//	.text("")
	//	.top(410)
	//	.left(10)
	//	.width(390)
	//	.height(300)
	//	.parent(m_Window)
	//.create();

	m_Keyboard = new keyboard();
	m_Mouse = new mouse();
#if 1

	l_Renderer = l_Control->get_renderer();

	math::size l_Size = l_Control->get_size();
	l_Targets = l_Renderer->create_render_target(l_Size.m_Width, l_Size.m_Height);
	l_Targets->attach(target_depth_stencil, "depth");
	l_Targets->attach(target_rgba, "diffuse");
	l_Targets->attach(target_rgba, "normal");
	l_Targets->attach(target_rgba, "other");

	//l_Renderer->get_shader_catalog()->m_OnCompileError += &compile_error_handler;
	s = l_Renderer->get_shader_catalog()->get_shader("posteffect");
	m_GeometryPass = l_Renderer->get_shader_catalog()->get_shader("geometrypass");
//	m_Code->set_text(m_GeometryPass->get_compiled_source(ps));

	// initialize mesh
	model &mo = *b.build_model(*l_Renderer);
	tex = mo.get_mesh(0)->get_material()->m_Diffuse;

	m_Exp = new expression_tree(mo);
	m_Exp->get_root()->add_input(new expression_animate(m_Exp, "walk"));

#if 0
	m_Operation.m_DrawMode = mode_triangle_list;
	int l_Offset = 0;
	l_Offset = m_Operation.m_Format.append_element(l_Offset, type_float3, semantic_position);
	l_Offset = m_Operation.m_Format.append_element(l_Offset, type_float3, semantic_normal);
	l_Offset = m_Operation.m_Format.append_element(l_Offset, type_float2, semantic_texture_coordinates);
	m_Operation.m_IndexBuffer = mo.get_mesh(0)->get_index_buffer();
	m_Operation.m_VertexBuffer = mo.get_mesh(0)->get_vertex_buffer();
#else
	m_Operation.m_DrawMode = mode_line_list;
	int l_Offset = 0;
	l_Offset = m_Operation.m_Format.append_element(l_Offset, type_float3, semantic_position);
	//skeleton = new krryn::data::md5_joint[b.m_AnimParser.m_Animation.m_NumFrames];

	//krryn::math::matrix l_CoordinateCorrection;
	//l_CoordinateCorrection.rotate_x(90);

	//// Transform skeleton
	//for(int i = 0; i < b.m_AnimParser.m_Animation.m_NumFrames; i++){
	//	data::md5_joint *skel = b.m_AnimParser.m_Animation.m_SkeletalFrames[i];
	//	for(int j = 0; j < b.m_AnimParser.m_Animation.m_NumFrames; j++){
	//		skel[j].m_Position = l_CoordinateCorrection.transform(skel[j].m_Position);
	//	}
	//}
#endif
	m_Diamond = diamond(*l_Renderer, 5, 2.5);

	matrix m = matrix::create_perspective_projection(1.f, 1000.f, 1, 0.75);
	l_Renderer->get_matrix_stack(matrix_projection).concatenate(m);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
#endif
}

void state_intro::activate(){}

void state_intro::deactivate(){}

void state_intro::tick(float a_DeltaTime){
	m_EventLoop.update();
	if(m_Keyboard->key_pressed(key_escape)) exit(0);

	static float z = 1;
	mouse_scroll l_Scroll =  m_Mouse->scroll_wheel();

	if(l_Scroll == scroll_up) z *= 1.2f;
	if(l_Scroll == scroll_down) z /= 1.2f;

	static float t = 0;
	t += a_DeltaTime;

	static float y = 0;
	y += 0.01;
	if(y > 1) y = 0;
#if 0
	{ 
		float dt = a_DeltaTime;
		int numFrames = b.m_AnimParser.m_Animation.m_NumFrames;
		m_AnimInfo.max_time = 1.f / b.m_AnimParser.m_Animation.m_FrameRate;
		_temp_animate(&b.m_AnimParser.m_Animation, &m_AnimInfo, dt);
		_temp_interp_skel(
			b.m_AnimParser.m_Animation.m_SkeletalFrames[m_AnimInfo.curr_frame], 
			b.m_AnimParser.m_Animation.m_SkeletalFrames[m_AnimInfo.next_frame], 
			b.m_AnimParser.m_Animation.m_NumFrames, 
			m_AnimInfo.last_time * b.m_AnimParser.m_Animation.m_FrameRate, 
			skeleton);
		
		int l_ElementCount = 0;
		for(int i = 0; i < numFrames; i++){
			if(skeleton[i].m_Parent != -1) l_ElementCount++;
		}

	}
#endif
	int l_ElementCount = 0;
	m_Exp->update(a_DeltaTime);
	skeleton *skeleton = m_Exp->get_result();

	for(int i = 0; i < skeleton->num_joints(); i++){
		if(skeleton->get_joint_parent(i) != skeleton::g_InvalidParent) l_ElementCount++;
	}

	if(!m_Operation.m_VertexBuffer.get()){
		m_Operation.m_VertexBuffer = l_Renderer->create_vertex_buffer(3 * sizeof(float), 2 * l_ElementCount);
		m_Operation.m_IndexBuffer  = l_Renderer->create_index_buffer(2 * l_ElementCount, view::index_16bit);
	}

	float *l_Buffer = (float*)m_Operation.m_VertexBuffer->lock(hardware_buffer::write);
	short *l_IB = (short*)m_Operation.m_IndexBuffer->lock(hardware_buffer::write);

	short u = 0;
	for(int i = 0; i < skeleton->num_joints(); i++){
		if(skeleton->get_joint_parent(i) != skeleton::g_InvalidParent){
			vector3f p1 = skeleton->get_joint_position(skeleton->get_joint_parent(i));

			*l_Buffer++ = p1.m_X;
			*l_Buffer++ = p1.m_Y;
			*l_Buffer++ = p1.m_Z;
			*l_IB++ = u;
			u++;

			vector3f p2 = skeleton->get_joint_position(i);

			*l_Buffer++ = p2.m_X;
			*l_Buffer++ = p2.m_Y;
			*l_Buffer++ = p2.m_Z;
			*l_IB++ = u;
			u++;
		}
	}

	m_Operation.m_IndexBuffer->unlock();
	m_Operation.m_VertexBuffer->unlock();

#if 1
	// geometry pass
	l_Targets->start_render();
	l_Renderer->clear_buffers(color_buffer | depth_buffer | stencil_buffer);

	matrix_stack &l_ModelView = l_Renderer->get_matrix_stack(matrix_modelview);

	matrix rot;
	//rot.rotate_y(t * 10);
	matrix rot1;
	rot1.rotate_x(90);
	rot.concatenate(rot1);

	l_ModelView.push();
	l_ModelView
		.scale(vector3f(0.1f, 0.1f, 0.1f))
		.concatenate(rot)
		.translate(vector3f(0, 0, -20));

	matrix mvp(l_ModelView.top());
	mvp.concatenate(l_Renderer->get_matrix_stack(matrix_projection).top());

	m_GeometryPass->bind_texture2d(ps, "diffuseMap", tex);

	tex->enable();
	m_GeometryPass->enable();
	l_Renderer->render(m_Operation);
	m_GeometryPass->disable();

	// Render skeleton
#if 0
	for(int i = 0; i < b.m_AnimParser.m_Animation.m_NumFrames; i++){
		if(skeleton[i].m_Parent != -1){
			vector3f dir = skeleton[skeleton[i].m_Parent].m_Position - skeleton[i].m_Position;
			dir = dir.normalize();

			l_ModelView.push();
			l_ModelView.translate(skeleton[skeleton[i].m_Parent].m_Position * 0.1);

			m_GeometryPass->enable();				
			l_Renderer->render(m_Diamond);
			m_GeometryPass->disable();

			l_ModelView.pop();
		}
	}
#endif
	tex->disable();
	l_ModelView.pop();
	l_Targets->end_render();

	//// light pass
	l_Renderer->clear_buffers(color_buffer | depth_buffer | stencil_buffer);

	matrix inv_proj = l_Renderer->get_matrix_stack(matrix_projection).top();
	inv_proj.invert();

	s->bind_textures_of_render_target(ps, l_Targets);
	s->bind_float4(ps, "lightpos", vector4f(10, 10, 10, 1));
	s->bind_matrix4x4(ps, "inverse_projection", inv_proj);
	s->bind_float(ps, "specular", z);

	s->enable();
		l_Targets->draw(*l_Renderer);
	s->disable();
	l_Control->swap_buffers();
#endif
}